3. Using System Property Schemas
As mentioned previously, a
number of system property schemas come with the product. Most of these
property schemas contain general BizTalk properties and the properties
to support each of the transports and adapters included with the product
out of the box. Most of the time, each new transport or application
adapter will bring with it a new property schema that is used to define
any custom metadata needed to process the message by the adapter. Each
of the system property schemas is included in the base assembly
Microsoft.BizTalk. GlobalPropertySchemas.dll. Referencing this assembly
from a BizTalk project in Visual Studio will allow you to access each of
the schemas as you would with any other schema type.
3.1. Modifying Values from System Property Schemas: Simple Example
So, at this point, many people
ask the question, "Big deal, I can modify values that BizTalk uses, so
why would I need this ability?" To fully understand why the creation of
property schemas becomes an invaluable tool, let's look at a simple
example. Continuing with the scenario from Exercise 3-1,
let's assume that an order received from the bulk order system needs to
be written to a file location on a server within the organization.
Let's also assume that we need to dynamically modify the name of this
file depending on some value from the message: the customer ID plus the
character # and the total amount of the order.
At first, modifying the file
name based on a message value seems like an easy thing to do, but it
becomes a little more complicated when you look at it. In reality, there
are three solutions to the problem. Solution A would be to use an
orchestration with a dynamic port and within the orchestration use some
XPath expressions to get the data you need from the message; dynamically
set the address of the file adapter; and send the file to the dynamic
port. However, in reality there is a cost to doing this. First, you are
breaking one of the cardinal rules of BizTalk—you are using
orchestrations to do routing logic. Second, you have an orchestration
that is exclusively bound to a port and has to be deployed, enlisted,
and started with the port it is bound to.
Third, if this were a large message, using XPath would force the
orchestration engine to load the entire document into memory in order to
parse out the values from the XPath expression.
Solution B is to use the
BizTalk Messaging Engine and have it do the work for you. To implement
this solution, you use the macro functionality to write the message in
the send port. You need to modify your internal order schema so that
there is a new element called OutBoundName
or something similar, and you need somewhere to promote this property
to. For this, use the file adapter property schema. In the internal
order schema, there is an element called ReceivedFileName. What you do is modify the BulkOrderToInternal.btm map so that the value of the CustomerID, along with the total, is concatenated into your new element in the internal schema. You still have to promote the CustomerID
property into the context. To do that, on the Property Promotions tab
of the schema, add a reference to the file adapter's property schema.
The next step is to create a
send port that subscribes to the correct message type, set the outbound
destination in the send port to the desired directory, and set the file
name to be %source-filename%. The BizTalk Messaging Engine will use
whatever value is stored in the ReceivedFileName
message context property when writing out this value. Since you have
changed it and promoted it, the engine will use your new value instead
of the original one, as shown in Figure 9.
Solution C is to create a
custom pipeline component, add it to a stage in a custom receive
pipeline, and have it promote the value you want into the context using
the message API. The code would look something like the following:
Private Sub PromoteProperties(ByVal message As IBaseMessage, ByVal CustomerID As _
String, ByVal OrderTotal As Decimal)
Dim BTSFilePropertiesNamespace As String = _
"http://schemas.microsoft.com/BizTalk/2003/file-properties"
Dim FileName As String
'Get the original directory the file was received from by reading the message _
context and creating a FileInfo object
Dim FileInfoObject As new
System.IO.FileInfo(message.Context.Read("ReceivedFileName", _
BTSFileropertiesNamespace))
'Replace the original name with the new one
FileName = FileInfoObject.DirectoryName + "\\ + CustomerID + "#" + _
OrderTotal.ToString() + ".xml"
message.Context.Promote("ReceivedFileName", BTSFileropertiesNamespace, FileName)
End Sub
The send port is
configured the same as in solution B. Although solution C uses the
Messaging Engine to accomplish the task, it requires you to write a
custom pipeline and pipeline component that you would then have to
maintain. The benefit of solution C is that it does not require you to
modify the internal schema or transformation in any way. Solution C
would be ideal if you had a production-emergency type of scenario where
you needed to implement the proposed change with as little possible
downtime or modification to the deployed solution. All you would need to
do is deploy the pipeline assembly to each of the BizTalk servers,
along with copying the pipeline component to the %Program
Files\Microsoft BizTalk Server 2009\Pipeline Components\ directory and
changing the pipeline configuration on the receive port. In this
scenario, there would be no server downtime and few configuration
changes required. The trade-off is that you would have additional custom
logic and custom assemblies that must be maintained and deployed with
the solution as a whole.
3.2. Modifying Values from System Property Schemas: Extended Example
Now that you see a
simplistic usage of modifying a system property, we will show you
something a little more interesting. Continuing with the previous
example, assume that you need to send information to your ERP system
with messages received from the web site. Also assume that there have
been performance problems calling the ERP solution. Currently the ERP
system uses a custom API written in VB 6.0 and exposed through COM+
objects. You need to track how long these calls are taking, but you want
the information included within the Messagebox, and you want it bundled
with all the other tracking information that is stored in the database
and accessible through MMC for the server administrators. You also want
individual tracking information per message so that you can correlate
what types of transactions are taking the most time.
Assume that the code to call
the API is fixed and cannot be modified. Due to architectural
limitations, you also cannot impose a wrapper (i.e., web service or
custom adapter), you must call the API directly as you would be
normally, and these calls must be synchronous because the API will not
support asynchronous calls because of threading issues. Currently, the
API is called from an Expression shape inside an orchestration, and
depending on a series of return values, different business logic is
executed. As an added bonus, the administrators want a copy of all
messages that take more than 5 seconds to process to be sent to a drop
location where those messages can be viewed offline. The development
team does not want any major logic modifications to the existing receive
ports/send ports/orchestrations to implement the logging logic (i.e.,
you can't store the tracking value somewhere and have the orchestration
insert a Decide shape that sends a copy to the send port). Also, message
tracking is not enabled, since this is a production system, and the
administrators do not want to decrease the performance of the system any
further.
Now that your hands are a
little more tied, the options are becoming a bit limited. Most people at
this point will want to modify the orchestration that logs information
either to the event log or to a performance counter, but that still
doesn't address the problem of how you can associate a message that you
processed with its timing values and have them show up somehow in MMC,
nor does it address the problem of how to properly route on those timing
values. Also, this breaks one of the cardinal rules: you use
orchestrations for something other than business logic. Another option
would be to create some custom tracking elements in the document and
write these from within the orchestration, but that would require a
schema change and some new code. Luckily, there is a better way, and it
requires only five lines of code be inserted into the orchestration
along with two variables.
BizTalk includes a tracking
property schema within the product. Although this schema is very poorly
documented (as in not at all), it is possible to write values to it. The
property schema is used to define context properties that adapters can
write to that will aid in the very type of scenario we're discussing
now. The fact that we can write values to the tracking property schema
really doesn't help since tracking is not enabled. Also, since the
performance bottleneck in this scenario is not based on an adapter, the
tracking information is not accurate. However, you can still use the
property schema to write information to the context from within the
orchestration that will help you, as shown in Figure 10.
The system property schema is
in the bts-messagetracking-properties.xsd schema file. By default, this
schema is populated with values from adapters, but there is nothing
that says you can't put your own values in here. Also, since the schema
is a property schema, the values will be available for routing. The
problem of how you can configure your send port to automatic pickup
times that are greater than 5 seconds has now been solved. What you can
do is create a little expression in the expression editor that gets the
current Now() time and subtracts it from the time that the operation finished. The StartTime
variable is a local variable defined as a System.DateTime that is
initialized to the current time within the orchestration. Since the
result will be of type System.Timespan, you cannot simply store it in
the property MessageTracking.AdapterTransmitEndTime since that property is of type System.DateTime. There is another property called MessageTracking.ActivityIdentity
that is of type string, which will allow you to store anything you
want. You simply store your computed time in that property and use it to
route your messages, as shown in Figure 11.
3.3. Custom Properties and Orchestration Routing
As demonstrated earlier, custom
properties can be very useful in routing scenarios. The previous example
showed how to route the message to an orchestration based on a custom
property and a subscription created via the filter expression. If you
were routing this message to an orchestration based on a filter defined
in the Receive shape of the orchestration, you would get the following
error:
"message data property 'ABCPropertySchema.CustomProperty' does not exist in
messagetype 'myOrchestrationMessage'"
What is happening in this
case is that the orchestration engine is examining the message within
the orchestration and throwing an error stating that the property you
want to route on does not exist in the message. Typically the engine is
correct; however, in this case, you know that this is okay. The solution
is to tell the engine that data for your property will not come from
the message and that you will provide it.
To fix this, you need to
set the Property Schema Base for the property under the Reference
section of the schema editor, as shown in Figure 12.
To the runtime, this
determines what the base type will be used for in the property in
question. The base type for the property will be used to determine where
the data for the property will come from. The possible values for this
are as follows:
MessageDataPropertyBase (Default): The data in this field will come from a message.
MessageContextPropertyBase:
The data in this field may not exist in a message (i.e., it could be
prompted inside a custom pipeline). The values will not be inside the
message.
PartContextPropertyBase: This tells the runtime that the value for the property will be part of the MessagePart context.
The key take-away is that if
you are promoting properties that do not exist in the message, be sure
to set the proper base type for the property (i.e.,
MessageContextPropertyBase).
As you can see, using
system properties can solve a number of rather complex scenarios with
very little effort.